using System;
using System.Threading;
using DZTC.Base32Utility.Core;

namespace DZTC.Base32Utility
{
    internal class BackgroundOperation
    {
        public delegate object OperationDelegate(object arg, CancellationToken ct, ProgressReporter pr);

        public delegate void OperationVoidDelegate(object arg, CancellationToken ct, ProgressReporter pr);

        public delegate void CompeteHandler(BackgroundOperation sender,
                                            CompleteEventArgs args);

        public class CompleteEventArgs : EventArgs
        {
            private Exception Exception { get; set; }
            private object Result { get; set; }

            public object QueryResult()
            {
                if (Exception != null) throw Exception;
                return Result;
            }

            public CompleteEventArgs(object result)
            {
                Result = result;
                Exception = null;
            }

            public CompleteEventArgs(Exception exception)
            {
                Exception = exception;
            }
        }


        private CancellationTokenSource OperationCancellation { get; set; }
        private ProgressReporter Reporter { get; set; }
        private OperationDelegate Operation { get; set; }
        private SynchronizationContext SynchronizationContext { get; set; }

        private bool Started { get; set; }

        public event CompeteHandler Complete;

        public event ProgressReporter.ProgressChangedEvent ProgressChanged
        {
            add { Reporter.ProgressChanged += value; }
            remove { Reporter.ProgressChanged -= value; }
        }

        private void OnComplete(CompleteEventArgs args)
        {
            var handler = Complete;
            if (handler != null)
            {
                if (SynchronizationContext != null)
                    SynchronizationContext.Post(_ => handler(this, args), null);

                else handler(this, args);
            }
        }

        private void OperationWrapper(Object param)
        {
            CompleteEventArgs args;

            try
            {
                var result = Operation(param, OperationCancellation.Token, Reporter);
                args = new CompleteEventArgs(result);
            }
            catch (Exception ex)
            {
                args = new CompleteEventArgs(ex);
            }

            OnComplete(args);
        }

        public void Cancel()
        {
            if (!Started) throw new InvalidOperationException("not started");
            OperationCancellation.Cancel();
        }

        public void Start(object param)
        {
            if (Started) throw new InvalidOperationException("already started");
            OperationCancellation = new CancellationTokenSource();

            Started = true;
            ThreadPool.QueueUserWorkItem(OperationWrapper, param);
        }

        /// <summary>
        /// Initializares new BackgroundOperation.
        /// </summary>
        /// <param name="operation">Operation to be executed.</param>
        /// <param name="sc">
        /// Syncronization context that will be used for event handlers invocation. 
        /// If sc is set to null, current context will be captured. 
        /// If current context is not set, event handlers will be invoked on thread, that caused the event.
        /// </param>
        public BackgroundOperation(OperationDelegate operation, SynchronizationContext sc = null)
        {
            SynchronizationContext = sc ?? SynchronizationContext.Current;
            Reporter = new ProgressReporter(SynchronizationContext);

            Operation = operation;
        }

        /// <summary>
        /// Initializares new BackgroundOperation.
        /// </summary>
        /// <param name="operation">Operation to be executed.</param>
        /// <param name="sc">
        /// Syncronization context that will be used for event handlers invocation. 
        /// If sc is set to null, current context will be captured. 
        /// If current context is not set, event handlers will be invoked on thread, that caused the event.
        /// </param>
        public BackgroundOperation(OperationVoidDelegate operation, SynchronizationContext sc = null)
            : this((arg, ct, pr) =>
                {
                    operation(arg, ct, pr);
                    return null;
                }, sc)
        {
        }
    }
}